﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Grinder.Exif;
using Grinder.Renamer.RenamePlugins;

namespace Grinder.Renamer
{
    // ILIST IN TOKEN CONTAINERS


    public sealed class FileRenamer
    {
        #region Private Fields
        private string _template;
        private string _replaceInvalidCharsWith = "";
        private readonly Regex _regex = new Regex(RenamerConstants.Regex);
        private IEnumerable<Token> _tokens;
        private readonly Dictionary<string, ICollection<Dictionary<string, object>>> _cache;        
        private readonly IList<IRenamePlugin> _renamePlugins;                
        #endregion

        #region Public Properties
        public IEnumerable<FindReplaceItem> FindReplaceItems { get; set; }

        public string ReplaceInvalidCharsWith
        {
            get { return _replaceInvalidCharsWith; }
            set { _replaceInvalidCharsWith = value; }
        }
        #endregion

        public FileRenamer()
        {
            FindReplaceItems = new List<FindReplaceItem>();

            _cache = new Dictionary<string, ICollection<Dictionary<string, object>>>();

            _renamePlugins = PluginManager.RenamePlugins;
        }

        public void ClearCache()
        {
            _cache.Clear();
        }

        private List<Token> ParseTokens(string template)
        {
            var tokens = new List<Token>();

            foreach (Match match in _regex.Matches(template))
            {
                string parsed = match.Groups["token"].ToString();

                if (!string.IsNullOrEmpty(parsed))
                    tokens.Add(new Token(parsed));
            }
            return tokens;
        }

        private string ProcessCoreTokens(FileInfo file, int counter)
        {
            string newFileName = _template;

            foreach (var token in _tokens)
            {
                string str = token.ToString();
                string fileName = file.GetNameWithoutExtension();

                if (token.Name.StartsWith("#"))
                    str = counter.Format(token.Name.Length);
                else if (token.Name.ToLower() == "substr")
                    if (token.Arguments.Count == 2)
                    {
                        try
                        {
                            int start = Convert.ToInt32(token.Arguments[0]);
                            int end = Convert.ToInt32(token.Arguments[1]);

                            if (start >= fileName.Length || start < 0) start = 0;
                            if (end > fileName.Length || end <= 0) end = fileName.Length;

                            str = fileName.Substring(start, end - start);
                        }
                        catch (FormatException) { }
                    }

                newFileName = newFileName.Replace(token.ToString(), str);
            }
            return newFileName;
        }

        private IEnumerable<IRenamePlugin> GetPlugins(FileInfo file)
        {
            var plugins = from s in _renamePlugins
                          where (s.Extensions.Contains("*" + file.Extension.ToUpper()) ||
                                 s.Extensions.Contains("*.*"))
                          select s;

            return plugins;
        }

        public void CacheFile(FileInfo file)
        {
            if (!_cache.ContainsKey(file.FullName))
            {
                var plugins = GetPlugins(file);

                var tokenContainers = new List<Dictionary<string, object>>();

                foreach (IRenamePlugin plugin in plugins)
                {
                    tokenContainers.Add(plugin.GetTokens(file));
                }

                _cache.Add(file.FullName, tokenContainers);
            }
        }

        private ICollection<Dictionary<string, object>> GetTokenContainers(FileInfo file)
        {
            ICollection<Dictionary<string, object>> tokenContainers = new List<Dictionary<string, object>>();

            if (_cache.ContainsKey(file.FullName))
                tokenContainers = _cache[file.FullName];
            else
            {
                var plugins = GetPlugins(file);

                foreach (var plugin in plugins)
                {
                    tokenContainers.Add(plugin.GetTokens(file));
                }

                _cache.Add(file.FullName, tokenContainers);
            }

            return tokenContainers;
        }

        public string GetNewFilename(string template, FileInfo file, int counter)
        {
            if (template == null) throw new ArgumentNullException("template");
            if (file == null) throw new ArgumentNullException("file");

            if (template != _template || _tokens == null)
            {
                _template = template;
                _tokens = ParseTokens(template);
            }

            var tokenContainers = GetTokenContainers(file);

            string newFileName = ProcessCoreTokens(file, counter);

            foreach (Token token in _tokens)
            {
                foreach (var container in tokenContainers)
                {
                    if (!container.ContainsKey(token.Name)) continue;
                    object tokenValue = container[token.Name];

                    if (tokenValue == null) continue;

                    int intValue;

                    if (int.TryParse(tokenValue.ToString(), out intValue))
                    {
                        if (token.Arguments.Count > 0 && token.Arguments[0].StartsWith("#"))
                            tokenValue = intValue.Format(token.Arguments[0].Length);
                    }
                    else if (tokenValue is DateTime)
                    {
                        var dateValue = (DateTime)tokenValue;

                        tokenValue = dateValue.ToString("G");

                        if (token.Arguments.Count() > 0)
                        {
                            try
                            {
                                tokenValue = dateValue.ToString(token.Arguments[0]);
                            }
                            catch (FormatException) { }
                        }
                    }
                    else if (tokenValue is GPSCoordinate)
                    {
                        var gpsValue = (GPSCoordinate)tokenValue;

                        if (token.Arguments.Count == 1)
                        {
                            string arg = token.Arguments[0];
                            if (arg.ToUpper() == "D")
                                tokenValue = gpsValue.Hours;
                            else if (arg.ToUpper() == "M")
                                tokenValue = gpsValue.Minutes;
                            else if (arg.ToUpper() == "S")
                                tokenValue = gpsValue.Seconds;
                        }
                    }

                    newFileName = newFileName.Replace(token.ToString(), tokenValue.ToString());
                }
            }

            newFileName = ProcessFindReplaceItems(newFileName);

            return TrimInvalidCharacters(newFileName, _replaceInvalidCharsWith) + file.Extension;
        }

        private static string TrimInvalidCharacters(string str, string replaceWith)
        {
            foreach (string s in RenamerConstants.InvalidChars)
                str = str.Replace(s, replaceWith);
            return str;
        }

        private string ProcessFindReplaceItems(string newFileName)
        {
            string str = newFileName;

            foreach (var item in FindReplaceItems)
                str = item.IsRegexp ?
                    Regex.Replace(str, item.Find, item.Replace) :
                    str.Replace(item.Find, item.Replace);

            return str;
        }

        public static void ProcessFile(ProcessFileParams processFileParams)
        {
            if (processFileParams == null) throw new ArgumentNullException("processFileParams");

            if (processFileParams.File == null) throw new ArgumentNullException("file");

            if ((processFileParams.ProcessingMode == ProcessingMode.Move || processFileParams.ProcessingMode == ProcessingMode.Copy)
                && string.IsNullOrEmpty(processFileParams.Destination))
                throw new ArgumentNullException("destination");

            FileInfo newFileInfo;

            if (processFileParams.ProcessingMode == ProcessingMode.Rename)
            {
                newFileInfo = new FileInfo(Path.Combine(processFileParams.File.Directory.FullName, processFileParams.NewFileName));
            }
            else
            {
                newFileInfo = new FileInfo(Path.Combine(processFileParams.Destination, processFileParams.NewFileName));
            }

            newFileInfo.Directory.Create();

            switch (processFileParams.ProcessingMode)
            {
                case ProcessingMode.Copy:
                    File.Copy(processFileParams.File.FullName, newFileInfo.FullName, processFileParams.Overwrite);
                    break;
                case ProcessingMode.Move:
                    File.Copy(processFileParams.File.FullName, newFileInfo.FullName, processFileParams.Overwrite);
                    File.Delete(processFileParams.File.FullName);
                    break;
                case ProcessingMode.Rename:
                    File.Copy(processFileParams.File.FullName, newFileInfo.FullName, processFileParams.Overwrite);
                    File.Delete(processFileParams.File.FullName);
                    break;
            }
        }
    }

    public enum ProcessingMode
    {
        Rename,
        Move,
        Copy
    }
}
